﻿using System;
using System.IO;
using System.Threading;
using UnityEngine;

namespace Kylin
{
    public class ProtoPackageLength
    {
        public const Int32 MAX_PACKAGE_LENGTH = 1024 * 1024 * 5;
        public const Int32 MAX_CLIENT_PACKET_LENGTH = 2048;
    }

    public enum EConnectionState
    {
        NONE = 0,
        CONNECTING = 1,
        CONNECTED = 2,
        CLOSED = 3
    }

    public class Connection
    {
		readonly int HEAD_SIZE = 2;

        public Connection()
        {
            this.State = EConnectionState.NONE;
			this.header = new byte[HEAD_SIZE];
        }

        private byte index;
        private readonly byte[] header;
        private NetworkImplement worker;
        private EConnectionState _state;
        public EConnectionState State
        {
            get { return _state; }
            set
            {
                _state = value;
                if (value == EConnectionState.NONE) return;
#if UNITY_EDITOR
                Debug.LogFormat("Network status switch to: {0}", value);
#endif
                NetworkManager.Instance.queueMsg(new byte[] {(byte) value});
            }
        }

        public void Connect(string host, int port)
        {
            if (this.worker != null)
            {
#if UNITY_EDITOR
                Debug.LogError("Connection.Connect duplicate called.");
                return;
#endif
            }

            Debug.Log("-------------------Connecting-----------------");
            this.State = EConnectionState.CONNECTING;

            var socket = new NetworkImplement(host, port);

            if (Interlocked.CompareExchange(ref worker, socket, null) == null)
            {
                socket.BeginConnect(host, port, ConnectCallBack, null);
            }
        }

        public void Close()
        {
            var socket = worker;

            if (socket == null)
            {
                return;
            }

            if (Interlocked.CompareExchange(ref worker, null, socket) == socket)
            {
                socket.Close();
                socket = null;
                this.State = EConnectionState.CLOSED;
                Debug.Log("Client close");
            }
        }

        private void closeOnError(Exception ex, NetworkImplement socket)
        {
			Debug.Log ("Connection closed on error." + ex.ToString());


            socket.Close();

            if (Interlocked.CompareExchange(ref worker, null, socket) != socket)
            {
                return;
            }
        }

        private void ConnectCallBack(IAsyncResult result)
        {
            var socket = (result as NetworkImplement.AsyncResult).Socket;
            try
            {
                socket.EndConnect(result);
            }
            catch (Exception ex)
            {
                closeOnError(ex, socket);
                return;
            }

            this.State = EConnectionState.CONNECTED;
            readHeader(socket);
        }

        private void readHeader(NetworkImplement socket)
        {
			socket.BeginRead(header, 0, HEAD_SIZE, HeaderCallback, null);
        }

        private void HeaderCallback(IAsyncResult result)
        {
            var socket = (result as NetworkImplement.AsyncResult).Socket;
            try
            {
                socket.EndRead(result);
            }
            catch (Exception ex)
            {
                closeOnError(ex, socket);
                return;
            }

            int size = ((int) (header[0]) << 8) | ((int) (header[1]) & 0x0000ff);
            readContent(size, socket);
        }

        private void readContent(int contentLength, NetworkImplement socket)
        {
            if (contentLength > ProtoPackageLength.MAX_PACKAGE_LENGTH)
            {
#if UNITY_EDITOR
                Debug.LogErrorFormat("消息包太大: {0}", contentLength.ToString());
#endif
                Close();
                return;
            }

            var buf = new byte[contentLength];
            socket.BeginRead(buf, 0, contentLength, RecvCallback, buf);
        }

        private void RecvCallback(IAsyncResult result)
        {
            var socket = (result as NetworkImplement.AsyncResult).Socket;

            if (socket != this.worker) return;

            try
            {
                socket.EndRead(result);
            }
            catch (Exception ex)
            {
                closeOnError(ex, socket);
                return;
            }

            var msg = result.AsyncState as byte[];

            NetworkManager.Instance.queueMsg(msg);

            readHeader(socket);
        }

        public void SendMessage(byte[] msg)
        {
            if (worker == null)
            {
                return;
            }

			if (msg.Length >= ProtoPackageLength.MAX_CLIENT_PACKET_LENGTH)
            {
#if UNITY_EDITOR
				Debug.LogErrorFormat("要发送的消息包太大, 最大长度: {0}, 现在要发送的长度: {1}", ProtoPackageLength.MAX_CLIENT_PACKET_LENGTH, msg.Length);
#endif
                Close();
                return;
            }

			worker.BeginWrite(msg, 0, msg.Length, SendCallBack, null);
        }

        private void SendCallBack(IAsyncResult result)
        {
            var socket = (result as NetworkImplement.AsyncResult).Socket;
            try
            {
                socket.EndWrite(result);
            }
            catch (Exception ex)
            {
                closeOnError(ex, socket);
                return;
            }
        }

        private void trace(String msg, Exception ex)
        {
#if UNITY_EDITOR
            Debug.LogErrorFormat(msg, ex);
#endif
        }
    }
}

